package com.zym.init.core.exception;

import com.alibaba.fastjson2.JSONObject;
import com.zym.init.core.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Set;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Slf4j
public class GlobalException {

    /**
     * 权限校验异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public AjaxResult handleAccessDeniedException(AccessDeniedException e) {
        log.error("权限校验异常：", e);
        return AjaxResult.permission("没有权限，请联系管理员授权");
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult runtimeExceptionHandler(RuntimeException e) {
        log.error("运行时出错：", e);
        return AjaxResult.error(getErrorMsg(e.getMessage()));
    }

    /**
     * 资源不存在异常
     */
    @ExceptionHandler(NoResourceFoundException.class)
    public AjaxResult noResourceFoundExceptionHandler(NoResourceFoundException e) {
        log.error("资源不存在：", e);
        return AjaxResult.notFound();
    }

    /**
     * 账号被封禁异常
     */
    @ExceptionHandler(BannedException.class)
    public AjaxResult bannedExceptionHandler(BannedException e) {
        log.error("账号被封禁：", e);
        return AjaxResult.permission(getErrorMsg(e.getMessage()));
    }

    /**
     * 业务错误
     */
    @ExceptionHandler(ServiceException.class)
    public AjaxResult businessExceptionHandler(ServiceException e) {
        log.error("业务错误：", e);
        return AjaxResult.serviceError(getErrorMsg(e.getMessage()));
    }

    /**
     * Validator 参数校验异常处理
     */
    @ExceptionHandler(value = BindException.class)
    public AjaxResult handleMethodVoArgumentNotValidException(BindException ex) {
        FieldError err = ex.getFieldError();
        String message = null;
        if (err != null) {
            message = "参数：{".concat(err.getField()).concat("}，").concat(Objects.requireNonNull(err.getDefaultMessage()));
        }
        if (err != null) {
            log.info("{} -> {}", err.getObjectName(), message);
        }
        return AjaxResult.serviceError(getErrorMsg(message));
    }

    /**
     * Validator 参数校验异常处理
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public AjaxResult handleMethodArgumentNotValidException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
            String paramName = pathImpl.getLeafNode().getName();
            String message = "参数：{".concat(paramName).concat("}，").concat(constraintViolation.getMessage());
            log.info("{} -> {} -> {}", constraintViolation.getRootBeanClass().getName(), pathImpl, message);
            return AjaxResult.serviceError(message);
        }
        return AjaxResult.serviceError(getErrorMsg(ex.getMessage()));
    }

    /**
     * 数据库连接错误
     */
    @ExceptionHandler(value = SQLException.class)
    public AjaxResult handleSQLException(SQLException ex) {
        log.error("数据库错误：", ex);
        return AjaxResult.error("数据库错误");
    }

    /**
     * 全局异常处理
     */
    @ExceptionHandler(value = Exception.class)
    public void handleException(Exception ex, HttpServletResponse response) {
        // 对于视频、音频、ogg媒体文件，不做处理
        if (response.getContentType() != null
                && (response.getContentType().contains("video/")
                || response.getContentType().contains("audio/")
                || response.getContentType().contains("application/ogg"))) {
        } else {
            log.error("系统内部错误：", ex);
            AjaxResult result = AjaxResult.error("系统内部错误");
            // 如果响应未提交，重置设置新的响应内容
            if (!response.isCommitted()) {
                response.reset();
                response.setStatus(HttpStatus.OK.value());
                response.setContentType("application/json;charset=UTF-8");
                try {
                    response.getWriter().write(JSONObject.toJSONString(result));
                } catch (IOException exc) {
                    throw new RuntimeException(exc);
                }
            }
        }
    }

    /**
     * 获取错误信息
     *
     * @param msg 错误信息
     * @return 错误信息
     */
    private String getErrorMsg(String msg) {
        return msg == null ? "系统内部未知错误" : msg;
    }

    @ExceptionHandler(RedisConnectionFailureException.class)
    public AjaxResult serviceExceptionHandler(RedisConnectionFailureException e) {
        log.error("redis服务连接失败：", e);
        return AjaxResult.error("redis服务连接失败");
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AjaxResult serviceExceptionHandler(HttpRequestMethodNotSupportedException e) {
        log.error("请求method错误：", e);
        return AjaxResult.error("请求method错误");
    }
}
